#pragma once
#include "opencv2/opencv.hpp"
#include "slStruct.h"
#include "inputParam.h"
#ifndef slLog
#define slLog Log::getInstance()
#endif

#ifndef PI
#define PI 3.14159265359f
#endif
namespace SL
{
    namespace pattern
    {
        typedef std::vector<bool> BitCode;
        typedef std::vector<BitCode> BitCodeList;
        BitCodeList generateGrayCode(int n)
        {
            BitCodeList bitCodeList;
            bitCodeList.resize(1i64 << n);
            if (n == 1)
            {
                BitCode bitCode0 = {0};
                BitCode bitCode1 = {1};
                bitCodeList[0] = bitCode0;
                bitCodeList[1] = bitCode1;
                return bitCodeList;
            }

            BitCodeList preBitCodeList = generateGrayCode(n - 1);
            int preBitCodeListNo = 1 << (n - 1);
            for (int i = 0; i < preBitCodeListNo; i++)
            {
                BitCode bitcode0 = {0};
                bitcode0.insert(bitcode0.end(), preBitCodeList[i].begin(), preBitCodeList[i].end());
                bitCodeList[i] = bitcode0;
                BitCode bitcode1 = {1};
                bitcode1.insert(bitcode1.end(), preBitCodeList[preBitCodeListNo - i - 1].begin(), preBitCodeList[preBitCodeListNo - i - 1].end());
                bitCodeList[preBitCodeListNo + i] = bitcode1;
            }
            return bitCodeList;
        }

        std::map<int, int> generateBitCodeMap(int grayCodeN)
        {
            BitCodeList bitCodeList = generateGrayCode(grayCodeN);
            std::map<int, int> bitCodeMap;
            for (int i = 0; i < (1 << grayCodeN); i++)
            {
                int x = 0;
                for (int n = 0; n < grayCodeN; n++)
                {
                    x += bitCodeList[i][n] != 0 ? (1 << (grayCodeN - n - 1)) : 0;
                }
                bitCodeMap.insert(std::pair<int, int>(x, i));
            }
            return bitCodeMap;
        };

        Images generateGrayCodePatternImages(int n, int width, int height, bool reverse = false, bool isVertical = true)
        {
            BitCodeList bitCodeList = generateGrayCode((n));
            Images images;
            for (int imageN = 0; imageN < n; imageN++)
            {
                Mat image = Mat::zeros(height, width, CV_8UC3);
                int size = isVertical ? (width - width % (1 << n)) / (1 << n) : (height - height % (1 << n)) / (1 << n);

                for (int i = 0; i < bitCodeList.size(); i++)
                {

                    bool bit = bitCodeList[i][imageN];
                    if (reverse)
                        bit = !bit;

                    if (isVertical)
                    {
                        for (int w = 0; w < size; w++)
                        {
                            for (int h = 0; h < height; h++)
                            {
                                image.at<Vec3b>(h, w + size * i) = bit ? Vec3b(255, 255, 255) : Vec3b(0, 0, 0);
                            }
                        }
                    }
                    else
                    {
                        for (int w = 0; w < width; w++)
                        {
                            for (int h = 0; h < size; h++)
                            {
                                image.at<Vec3b>(h + size * i, w) = bit ? Vec3b(255, 255, 255) : Vec3b(0, 0, 0);
                            }
                        }
                    }
                }
                images.push_back(image);
            }
            return images;
        }

        Images generateGrayCodePatternImagesWOffset(int n, int width, int height, bool reverse = false, bool isVertical = true)
        {
            BitCodeList bitCodeList = generateGrayCode((n));
            Images images;
            int size, offset;
            if (isVertical)
            {
                size = width % (1 << n) == 0 ? (width - width % (1 << n)) / (1 << n) : (width - width % (1 << n)) / (1 << n) + 1;
                offset = (size * (1 << n) - width) / 2;
            }
            else
            {
                size = height % (1 << n) == 0 ? (height - height % (1 << n)) / (1 << n) : (height - height % (1 << n)) / (1 << n) + 1;
                offset = (size * (1 << n) - height) / 2;
            }

            for (int imageN = 0; imageN < n; imageN++)
            {
                Mat image = Mat::zeros(height, width, CV_8UC3);
                for (int i = 0; i < bitCodeList.size(); i++)
                {
                    bool bit = bitCodeList[i][imageN];
                    if (reverse)
                        bit = !bit;

                    if (isVertical)
                    {
                        for (int w = 0; w < size; w++)
                        {
                            int x = w + size * i - offset;
                            if (x < 0 || x > width - 1)
                                continue;
                            for (int h = 0; h < height; h++)
                            {
                                image.at<Vec3b>(h, x) = bit ? Vec3b(255, 255, 255) : Vec3b(0, 0, 0);
                            }
                        }
                    }
                    else
                    {
                        for (int h = 0; h < size; h++)
                        {
                            int y = h + size * i - offset;
                            if (y < 0 || y > height - 1)
                                continue;
                            for (int w = 0; w < width; w++)
                            {
                                image.at<Vec3b>(y, w) = bit ? Vec3b(255, 255, 255) : Vec3b(0, 0, 0);
                            }
                        }
                    }
                }
                images.push_back(image);
            }
            return images;
        }

        Images generateWhiteBlackImages(int width, int height)
        {
            Images images;
            Mat imageBlack(height, width, CV_8UC3);
            Mat imageWhite(height, width, CV_8UC3);
            for (int h = 0; h < height; h++)
            {
                for (int w = 0; w < width; w++)
                {
                    imageWhite.at<Vec3b>(h, w) = Vec3b(255, 255, 255);
                    imageBlack.at<Vec3b>(h, w) = Vec3b(0, 0, 0);
                }
            }
            images.push_back(imageWhite);
            images.push_back(imageBlack);
            return images;
        }

        Images generatePhaseShiftPatternImages(ComplementaryGrayWithPhaseShiftInputParam complementaryGrayWithPhaseShiftInputParam, bool isVertical = true)
        {
            float A = complementaryGrayWithPhaseShiftInputParam.A;
            float B = complementaryGrayWithPhaseShiftInputParam.B;
            int n = complementaryGrayWithPhaseShiftInputParam.phaseShiftN;
            int grayCodeN = complementaryGrayWithPhaseShiftInputParam.grayCodeN;
            int stripNo = 1 << grayCodeN;
            int width = complementaryGrayWithPhaseShiftInputParam.projectorWidth;
            int height = complementaryGrayWithPhaseShiftInputParam.projectorHeight;
            int size = isVertical ? (width - width % stripNo) / stripNo : (height - height % stripNo) / stripNo;
            Images images;
            for (int i = 0; i < n; i++)
            {
                Mat image = Mat::zeros(height, width, CV_8UC1);
                if (isVertical)
                {
                    for (int w = 0; w < size; w++)
                    {
                        double phase = cos((float)w / (float)size * 2 * PI + 2.0f / (float)n * PI * i);
                        uint8_t p = static_cast<uint8_t>(A + B * phase);
                        for (int stripId = 0; stripId < stripNo; stripId++)
                        {
                            for (int h = 0; h < height; h++)
                            {
                                image.at<uint8_t>(h, stripId * size + w) = p;
                            }
                        }
                    }
                }
                else
                {
                    for (int h = 0; h < size; h++)
                    {
                        double phase = cos((float)h / (float)size * 2 * PI + 2.0f / (float)n * PI * i);
                        uint8_t p = static_cast<uint8_t>(A + B * phase);
                        for (int stripId = 0; stripId < stripNo; stripId++)
                        {
                            for (int w = 0; w < width; w++)
                            {
                                image.at<uint8_t>(stripId * size + h, w) = p;
                            }
                        }
                    }
                }

                images.push_back(image);
            }
            return images;
        }
    }
}